﻿using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace CWM.Skinn
{
    class PrefabProcessor : AssetPostprocessor
    {
        static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
        {
            foreach (string path in importedAssets)
            {
                if (!path.EndsWith(".prefab")) continue;

                var prefab = (GameObject)AssetDatabase.LoadAssetAtPath(path, typeof(GameObject));
                if (!prefab) continue;
                var instance = FindNewPrefabInstance(prefab);
                CreateUnsavedAssets(path, prefab, instance);
            }
        }

        public static GameObject FindNewPrefabInstance(GameObject prefab)
        {
            var contextRoot = Array.Find(UnityEngine.Object.FindObjectsOfType<GameObject>(),
                            (x) => {
#if UNITY_2018_3_OR_NEWER
                                return PrefabUtility.IsAnyPrefabInstanceRoot(x) &&
                               PrefabUtility.GetCorrespondingObjectFromOriginalSource(x) == prefab;
#else
                                return PrefabUtility.GetPrefabType(x) != PrefabType.None &&
#if UNITY_2018_1_OR_NEWER
                                      PrefabUtility.GetCorrespondingObjectFromSource(x) == prefab;
#else

#if UNITY_2017_4_OR_NEWER
                             PrefabUtility.GetPrefabParent(x) == prefab;
#else
                             EditorUtility.GetPrefabParent(x) == prefab;
#endif
#endif
#endif
                            });
            return contextRoot;
        }

        public static void CreateUnsavedAssets(string path, GameObject prefab, GameObject instance)
        {
            var assetPath = path.Replace(prefab.name + ".prefab", "");
            var names = new List<string>();
            var newMeshes = new Dictionary<int, string>();
            var assetRenderers = prefab.GetRenderers().ToArray();
            var sceneRenderers = instance.GetRenderers().ToArray();
            var count = UnsavedMeshCount(assetRenderers, sceneRenderers); if (count < 1) return;

            if (!EditorUtility.DisplayDialog(
                "Unsaved Assets",
                string.Format("{0} unsaved meshes found, would you like to save them? Warning! this can overwrite existing meshes!", count),
            "Save",
            "Cancel")) return;

            SaveMeshes(assetPath, ref names, ref newMeshes, assetRenderers, sceneRenderers);
            if (newMeshes.Count < 1) return;

            AssetDatabase.Refresh();
            LoadMeshes(newMeshes, assetRenderers, sceneRenderers);

#if UNITY_2018_3_OR_NEWER
            PrefabUtility.ApplyPrefabInstance(instance, InteractionMode.AutomatedAction);
#else

#if UNITY_2017_4_OR_NEWER
            PrefabUtility.ReconnectToLastPrefab(instance);
#else
            EditorUtility.ReconnectToLastPrefab(instance);
#endif
#endif

            AssetDatabase.SaveAssets();
        }


        private static int UnsavedMeshCount(
           Renderer[] asset,
           Renderer[] scene)
        {
            var count = 0;
            var ids = new List<int>();
            var minCount = Mathf.Min(asset.Length, scene.Length);
            for (int i = 0; i < minCount; i++)
            {
                var assetMesh = asset[i].GetSharedMesh(); if (assetMesh) continue;
                var sceneMesh = scene[i].GetSharedMesh(); if (!sceneMesh) continue;
                if (!ids.Contains(sceneMesh.GetInstanceID())) { count++; ids.Add(sceneMesh.GetInstanceID()); }
            }
            return count;
        }

        private static void SaveMeshes(
            string assetPath,
            ref List<string> names,
            ref Dictionary<int, string> newMeshes,
            Renderer[] asset,
            Renderer[] scene)
        {
            var count = Mathf.Min(asset.Length, scene.Length);
            for (int i = 0; i < count; i++)
            {
                var assetMesh = asset[i].GetSharedMesh(); if (assetMesh) continue;
                var sceneMesh = scene[i].GetSharedMesh(); if (!sceneMesh) continue;
                string savedMesh;
                if (!newMeshes.TryGetValue(sceneMesh.GetInstanceID(), out savedMesh))
                {
                    var meshAssetPath = assetPath + GetAssetName(ref names, sceneMesh.name) + ".asset";
                    AssetDatabase.CreateAsset(sceneMesh.Clone() as Mesh, meshAssetPath);
                    newMeshes.Add(sceneMesh.GetInstanceID(), meshAssetPath);
                }
            }
        }

        private static void LoadMeshes(Dictionary<int, string> newMeshes, Renderer[] asset, Renderer[] scene)
        {
            var count = Mathf.Min(asset.Length, scene.Length);
            for (int i = 0; i < count; i++)
            {
                var assetMesh = asset[i].GetSharedMesh(); if (assetMesh) continue;
                var sceneMesh = scene[i].GetSharedMesh(); if (!sceneMesh) continue;
                string savedMesh;
                if (!newMeshes.TryGetValue(sceneMesh.GetInstanceID(), out savedMesh)) continue;
                var newMesh = (Mesh)AssetDatabase.LoadAssetAtPath(savedMesh, typeof(Mesh)); if (!newMesh) continue;
                asset[i].SetSharedMesh(newMesh);
                scene[i].SetSharedMesh(newMesh);
            }
        }

        private static string GetAssetName(ref List<string> names, string name)
        {
            string meshName = string.IsNullOrEmpty(name) ? "NoName" : name;
            if (meshName.Length > "(Clone)".Length) meshName = meshName.Replace("(Clone)", "");
            int count = 0;
            string validName = meshName;
            while (names.Contains(validName))
            {
                count++;
                validName = string.Format("{0}_{1}", meshName, count);
            }
            names.Add(validName);
            return validName;
        }
    }
}